use serde::{Deserialize, Serialize};

use crate::{Hash256Bytes, constants};

#[derive(Serialize, Deserialize, Eq, PartialEq, PartialOrd, Hash, Clone, Copy)]
pub struct BlockChainHash(Hash256Bytes); // sha2::Sha256 -- 256 bits;

impl std::fmt::Display for BlockChainHash {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.to_hex_string())
    }
}

impl std::fmt::Debug for BlockChainHash {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.to_hex_string())
    }
}

impl BlockChainHash {
    pub fn new(hash: Hash256Bytes) -> Self {
        Self(hash)
    }

    pub fn new_null() -> Self {
        Self(constants::NULL_BLOCK_ID)
    }

    pub fn is_null(&self) -> bool {
        self.0 == constants::NULL_BLOCK_ID
    }

    pub fn as_bytes_ref(&self) -> &Hash256Bytes {
        &self.0
    }

    pub fn from_hex_str(hex_string: &str) -> Result<Self, hex::FromHexError> {
        let bytes = hex::decode(hex_string)?;
        if bytes.len() != constants::BLOCK_CHAIN_HASH_BYTES_LENGTH {
            return Err(hex::FromHexError::InvalidStringLength);
        }
        let mut hash_bytes = [0; constants::BLOCK_CHAIN_HASH_BYTES_LENGTH];
        hash_bytes.copy_from_slice(&bytes);
        Ok(Self(hash_bytes))
    }

    pub fn as_bytes(&self) -> Hash256Bytes {
        self.0
    }

    pub fn to_hex_string(&self) -> String {
        hex::encode(self.0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_blockchain_hash() {
        #[rustfmt::skip]
        let hash = BlockChainHash::new([
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
            0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
            0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
        ]);

        assert_eq!(
            hash.to_string(),
            format!(
                "{}{}",
                "0102030405060708090a0b0c0d0e0f10", "1112131415161718191a1b1c1d1e1f20"
            )
        );
    }

    #[test]
    fn test_serde_json_blockchain_hash() {
        #[rustfmt::skip]
        let hash = BlockChainHash::new([
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
            0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
            0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
            0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
        ]);

        let json_str = r#"[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]"#;

        assert_eq!(serde_json::to_string(&hash).unwrap(), json_str);
        assert_eq!(
            serde_json::from_str::<BlockChainHash>(json_str).unwrap(),
            hash
        );
    }

    #[test]
    fn test_serde_bincode_blockchain_hash() {
        #[rustfmt::skip]
        let hash_bytes: [u8; 32] = [
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
        ];

        let hash = BlockChainHash::new(hash_bytes);
        let serialized = bincode::serialize(&hash).unwrap();
        let vec = Vec::from(hash_bytes);
        assert_eq!(serialized, vec);

        let deserialized: BlockChainHash = bincode::deserialize(&serialized).unwrap();
        assert_eq!(deserialized, hash);
    }

    #[test]
    fn test_blockchain_hash_from_hex_string() {
        let hex_string = "f000000000000000000000000000000000000000000000000000000000000001";
        let hash = BlockChainHash::from_hex_str(hex_string).unwrap();
        assert_eq!(hash.to_string(), format!("{}", hex_string))
    }

    #[test]
    fn test_compare_block_chain_hash_big_endian() {
        let h1 = BlockChainHash::from_hex_str(
            "0000000000000000000000000000000000000000000000000000000000000001",
        )
        .unwrap();
        let h2 = BlockChainHash::from_hex_str(
            "0010000000000000000000000000000000000000000000000000000000000000",
        )
        .unwrap();

        assert!(h1 < h2);
    }

    #[test]
    fn test_compare_block_chain_hash() {
        let h1 = BlockChainHash::from_hex_str(
            "0070000000000000000000000000000000000000000000000000000000000001",
        )
        .unwrap();
        let h2 = BlockChainHash::from_hex_str(
            "0080000000000000000000000000000000000000000000000000000000000001",
        )
        .unwrap();
        assert!(h1 < h2);
    }
}
